/***************************************************
 * 中文点阵字库显示-内核插件版
 ****************************************************/
#include <vitasdkkern.h>
#include <taihen.h>

#include "main.h"
#include "scr_print.h"
#include "menu.h"
#include "gui.h"
#include "config.h"
#include "utils.h"

enum AppType
{
	APP_HOME,
	APP_SYSTEM,
	APP_GAME,
	APP_BLACKLISTED,
};

#define FRAMEBUF_HOOK_MAGIC 0x7294016

#define MAX_HOOKS_NUM 14
static tai_hook_ref_t s_hook_refs[MAX_HOOKS_NUM];
static SceUID s_hooks[MAX_HOOKS_NUM];

#define HOME_TITLEID "HOME"
#define MAX_TITLEID_LENGTH 16
char g_titleid[MAX_TITLEID_LENGTH] = HOME_TITLEID;
int g_app_type = APP_HOME;
uint8_t g_is_dolce = 0;

#define INVALID_PID -1
SceUID s_pid = INVALID_PID;

static SceUID s_framebuf_mutex_uid = -1;
static SceUID s_procevent_mutex_uid = -1;

static SceUID s_thread_uid = -1;
static uint8_t s_thread_run = 1;

extern int module_get_export_func(SceUID pid, const char *modname, uint32_t libnid, uint32_t funcnid, uintptr_t *func);
extern int module_get_offset(SceUID pid, SceUID modid, int segidx, size_t offset, uintptr_t *addr);
extern SceBool ksceAppMgrIsExclusiveProcessRunning();

int exitGame()
{
	if (g_app_type != APP_HOME && g_app_type != APP_BLACKLISTED && s_pid != INVALID_PID && s_pid != KERNEL_PID)
		return ksceAppMgrKillProcess(s_pid);
	return 0;
}

/* 线程，用来监听按键呼出菜单，当然也可以做些其它的 */
static int mainThreadEntry(SceSize args, void *argp)
{
	while (s_thread_run)
	{
		// 黑名单里不做任何事，主要是为了PSP模拟器的问题
		if (g_app_type == APP_BLACKLISTED)
		{
			ksceKernelDelayThread(200 * 1000);
			continue;
		}

		// 读取按键，以判断打开菜单
		SceCtrlData ctrl;
		memset(&ctrl, 0, sizeof(SceCtrlData));
		int ret = ksceCtrlPeekBufferPositive(0, &ctrl, 1);
		if (ret < 0)
			ret = ksceCtrlPeekBufferPositive(1, &ctrl, 1);
		if (ret >= 0)
		{
			if (!isShowingMenu())
			{
				readPad(&ctrl, 0);
				if (isKeyCurrent(SCE_CTRL_LTRIGGER) && isKeyCurrent(SCE_CTRL_SELECT))
					openMenu();
			}
		}

		ksceKernelDelayThread(50 * 1000);
	}

	return 0;
}

int startMainThread()
{
	s_thread_uid = ksceKernelCreateThread("kernel_main_thread", mainThreadEntry, 0x3C, 0x3000, 0, 0x10000, 0);
	if (s_thread_uid < 0)
		return s_thread_uid;
	return ksceKernelStartThread(s_thread_uid, 0, NULL);
}

void finishMainThread()
{
	if (s_thread_uid >= 0)
	{
		s_thread_run = 0;
		ksceKernelWaitThreadEnd(s_thread_uid, NULL, NULL);
		ksceKernelDeleteThread(s_thread_uid);
		s_thread_uid = -1;
	}
}

/* Hook帧设置，用来修改帧数据以显示需要的UI */
static int ksceDisplaySetFrameBufInternal_patched(int head, int index, const SceDisplayFrameBuf *pParam, int sync)
{
	static uint8_t is_in_my_magic = 0;

	if (sync == FRAMEBUF_HOOK_MAGIC) // 检测到我的魔法标记，把sync还原再传递一遍
	{								 // 魔法帧不记录帧数，影响超频显示，它并非游戏的真实帧数
		sync = SCE_DISPLAY_SETBUF_NEXTFRAME;
		goto DISPLAY_HOOK_RET;
	}
	else if ((sync != SCE_DISPLAY_SETBUF_IMMEDIATE && sync != SCE_DISPLAY_SETBUF_NEXTFRAME)) // 其它插件使用了魔法标记
	{																						 // 原样放行，让他们自己处理
		if (isShowingMenu())																 // 如果打开了菜单
			closeMenu();																	 // 关掉它，避免冲突
		goto DISPLAY_HOOK_RET;																 // 魔法帧不记录帧数，影响超频显示，如果能hook到的话
	}
	else if (is_in_my_magic)   // 我的魔法标记被其它插件拦截修改了，原样放行，等他们hook完
	{						   // 魔法帧被hook中，不记录帧数，影响超频显示
		if (isShowingMenu())   // 如果打开了菜单
			closeMenu();	   // 关掉它，避免冲突
		goto DISPLAY_HOOK_RET; // 魔法帧不记录帧数，影响超频显示
	}

	if (g_app_type == APP_BLACKLISTED)
		goto DISPLAY_HOOK_RET;

	/* 判断有些帧不构画，具体资料未知，应该有缓存帧 */
	if (head != ksceDisplayGetPrimaryHead() || !pParam || !pParam->base)
		goto DISPLAY_HOOK_RET;

	if (index && (ksceAppMgrIsExclusiveProcessRunning() || g_app_type == APP_GAME)) // Do not draw over SceShell overlay
		goto DISPLAY_HOOK_RET;

	if (ksceKernelLockMutex(s_framebuf_mutex_uid, 1, NULL) < 0) // 锁的作用为防闪烁，不用可能会被同时执行，具体未细测试，仅猜测
		goto DISPLAY_HOOK_RET;

	if (guiIsShowing())
	{
		scrPrintSetFrameBuf(pParam); // 设置当前的帧数据信息
		guiDrawAll();				 // 构画UI
		guiCtrlAll();				 // 控制UI

		if (isShowingMenu() && sync == SCE_DISPLAY_SETBUF_NEXTFRAME && g_app_type == APP_GAME)
		{ // UI较多较大较复杂时，立即更新帧以修复错过vblank周期时的闪烁
			ksceKernelUnlockMutex(s_framebuf_mutex_uid, 1);

			int ret = TAI_CONTINUE(int, s_hook_refs[0], head, index, pParam, SCE_DISPLAY_SETBUF_IMMEDIATE);
			is_in_my_magic = 1;
			ret = ksceDisplaySetFrameBufInternal(head, index, pParam, FRAMEBUF_HOOK_MAGIC);
			is_in_my_magic = 0;
			return ret;
		}
	}

	ksceKernelUnlockMutex(s_framebuf_mutex_uid, 1);

DISPLAY_HOOK_RET:
	return TAI_CONTINUE(int, s_hook_refs[0], head, index, pParam, sync);
}

/* Hook用户按键，用来在菜单开启时清空用户的按键值 */
static int ctrlPatched(tai_hook_ref_t hook_ref, int port, SceCtrlData *ctrl, int count, int negative)
{
	if (hook_ref == 0)
		return 1;

	int ret = TAI_CONTINUE(int, hook_ref, port, ctrl, count);
	if (ret < 0 || (port != 0 && port != 1))
		return ret;

	if (isShowingMenu())
	{
		uint32_t buttons = negative ? 0xFFFFFFFF : 0;
		ksceKernelMemcpyKernelToUser(&ctrl->buttons, &buttons, sizeof(uint32_t));
	}

	return ret;
}

static int sceCtrlPeekBufferPositive_patched(int port, SceCtrlData *ctrl, int count)
{
	return ctrlPatched(s_hook_refs[1], port, ctrl, count, 0);
}

static int sceCtrlPeekBufferPositive2_patched(int port, SceCtrlData *ctrl, int count)
{
	return ctrlPatched(s_hook_refs[2], port, ctrl, count, 0);
}

static int sceCtrlReadBufferPositive_patched(int port, SceCtrlData *ctrl, int count)
{
	return ctrlPatched(s_hook_refs[3], port, ctrl, count, 0);
}

static int sceCtrlReadBufferPositive2_patched(int port, SceCtrlData *ctrl, int count)
{
	return ctrlPatched(s_hook_refs[4], port, ctrl, count, 0);
}

static int sceCtrlPeekBufferPositiveExt_patched(int port, SceCtrlData *ctrl, int count)
{
	return ctrlPatched(s_hook_refs[5], port, ctrl, count, 0);
}

static int sceCtrlPeekBufferPositiveExt2_patched(int port, SceCtrlData *ctrl, int count)
{
	return ctrlPatched(s_hook_refs[6], port, ctrl, count, 0);
}

static int sceCtrlReadBufferPositiveExt_patched(int port, SceCtrlData *ctrl, int count)
{
	return ctrlPatched(s_hook_refs[7], port, ctrl, count, 0);
}

static int sceCtrlReadBufferPositiveExt2_patched(int port, SceCtrlData *ctrl, int count)
{
	return ctrlPatched(s_hook_refs[8], port, ctrl, count, 0);
}

static int sceCtrlPeekBufferNegative_patched(int port, SceCtrlData *ctrl, int count)
{
	return ctrlPatched(s_hook_refs[9], port, ctrl, count, 1);
}

static int sceCtrlPeekBufferNegative2_patched(int port, SceCtrlData *ctrl, int count)
{
	return ctrlPatched(s_hook_refs[10], port, ctrl, count, 1);
}

static int sceCtrlReadBufferNegative_patched(int port, SceCtrlData *ctrl, int count)
{
	return ctrlPatched(s_hook_refs[11], port, ctrl, count, 1);
}

static int sceCtrlReadBufferNegative2_patched(int port, SceCtrlData *ctrl, int count)
{
	return ctrlPatched(s_hook_refs[12], port, ctrl, count, 1);
}

/* Hook调用过程事件处理函数，用来判断当前桌面或游戏内及获取游戏ID等 */
int ksceKernelInvokeProcEventHandler_patched(int pid, int ev, int a3, int a4, int *a5, int a6)
{
	int ret = ksceKernelLockMutex(s_procevent_mutex_uid, 1, NULL);
	if (ret < 0)
		goto PROCEVENT_EXIT;

	char titleid[MAX_TITLEID_LENGTH];
	strncpy(titleid, g_titleid, MAX_TITLEID_LENGTH); // 复制当前titleid，为之后判断是否变更做依据

	switch (ev)
	{
	case 1:								// startup
	case 5:								// resume
		if (g_app_type != APP_HOME)		/* 系统休眠或游戏退出时会由3、4重设置为home，所以有变更游戏一定会 */
			goto PROCEVENT_UNLOCK_EXIT; /* 经过HOME，若不是则当前位置无变更，跳过无需处理，具体未细测试，用着没什么问题 */

		ksceKernelGetProcessTitleId(pid, titleid, sizeof(titleid));
		// Check app type
		if (ksceSblACMgrIsPspEmu(pid)) // PspEmu比较特殊，未细了解，非一般应用程序，拉黑它，因为我们一般也不会需要对它做些什么，它可能会产生一些bug
			g_app_type = APP_BLACKLISTED;
		else if (strncmp(titleid, "NPXS10079", 9) == 0) // 系统的什么检查器，一般启动时经常冒出来，忽略它将其视为HOME，防止频繁的ID变更
			goto PROCEVENT_UNLOCK_EXIT;
		else if (strncmp(titleid, "NPXS", 4) == 0) // NPXS开头的ID为系统程序
			g_app_type = APP_SYSTEM;
		else
			g_app_type = APP_GAME;
		break;

	case 3:					   // exit
	case 4:					   // suspend
		g_app_type = APP_HOME; /* 退出游戏与休眠时重设置为HOME */
		strncpy(titleid, HOME_TITLEID, MAX_TITLEID_LENGTH);
		break;
	}

	/* 其它的事件没有更多资料参考，目前只监听启动、退出、休眠、唤醒，为了获取当前位置变更信息 */
	if (ev == 1 || ev == 5 || ev == 3 || ev == 4)
	{ // Check titleid changed
		if (strcmp(g_titleid, titleid) != 0)
		{
			saveConfig();									  // 保存当前titleid的配置文件
			strncpy(g_titleid, titleid, MAX_TITLEID_LENGTH);  // 更新新的titleid
			s_pid = (ev == 1 || ev == 5) ? pid : INVALID_PID; // 设定当前pid，退出及休眠时设为-1，也就是说HOME为-1，pid可以用来强制退出游戏

			if (g_app_type == APP_BLACKLISTED)
			{ // 如果是黑名单则不做任何事，（示范案例为将配置重设，因为我的配置初始值是不做任何事，你可以有自己的黑名单处理）
				resetConfig();
				if (guiIsShowing()) // 如果当前的菜单UI开启着，应当关闭它
					guiCloseAll();
			}
			else // 加载当前新的titleid的配置信息
			{
				loadConfig();
			}
		}
	}

PROCEVENT_UNLOCK_EXIT:
	ksceKernelUnlockMutex(s_procevent_mutex_uid, 1);

PROCEVENT_EXIT:
	return TAI_CONTINUE(int, s_hook_refs[13], pid, ev, a3, a4, a5, a6);
}

void _start() __attribute__((weak, alias("module_start")));
int module_start(SceSize argc, const void *args)
{
	if (ksceSblAimgrIsGenuineDolce()) // 判断是不是psvtv
		g_is_dolce = 1;

	s_framebuf_mutex_uid = ksceKernelCreateMutex("kernel_framebuf_mutex", 0, 0, NULL); // 创建帧设置的锁
	s_procevent_mutex_uid = ksceKernelCreateMutex("kernel_procevent_mutex", 0, 0, NULL);

	loadConfig();

	/* Hook帧设置函数 */
	s_hooks[0] = taiHookFunctionExportForKernel(KERNEL_PID, &s_hook_refs[0], "SceDisplay", 0x9FED47AC, 0x16466675, ksceDisplaySetFrameBufInternal_patched);

	/* 用户层按键监听，此示范仅做清空按键需求。Negative应为反值，几乎没有游戏用它，未细测试 */
	s_hooks[1] = taiHookFunctionExportForKernel(KERNEL_PID, &s_hook_refs[1], "SceCtrl", 0xD197E3C7, 0xA9C3CED6, sceCtrlPeekBufferPositive_patched);
	s_hooks[2] = taiHookFunctionExportForKernel(KERNEL_PID, &s_hook_refs[2], "SceCtrl", 0xD197E3C7, 0x15F81E8C, sceCtrlPeekBufferPositive2_patched);
	s_hooks[3] = taiHookFunctionExportForKernel(KERNEL_PID, &s_hook_refs[3], "SceCtrl", 0xD197E3C7, 0x67E7AB83, sceCtrlReadBufferPositive_patched);
	s_hooks[4] = taiHookFunctionExportForKernel(KERNEL_PID, &s_hook_refs[4], "SceCtrl", 0xD197E3C7, 0xC4226A3E, sceCtrlReadBufferPositive2_patched);
	s_hooks[5] = taiHookFunctionExportForKernel(KERNEL_PID, &s_hook_refs[5], "SceCtrl", 0xD197E3C7, 0xA59454D3, sceCtrlPeekBufferPositiveExt_patched);
	s_hooks[6] = taiHookFunctionExportForKernel(KERNEL_PID, &s_hook_refs[6], "SceCtrl", 0xD197E3C7, 0x860BF292, sceCtrlPeekBufferPositiveExt2_patched);
	s_hooks[7] = taiHookFunctionExportForKernel(KERNEL_PID, &s_hook_refs[7], "SceCtrl", 0xD197E3C7, 0xE2D99296, sceCtrlReadBufferPositiveExt_patched);
	s_hooks[8] = taiHookFunctionExportForKernel(KERNEL_PID, &s_hook_refs[8], "SceCtrl", 0xD197E3C7, 0xA7178860, sceCtrlReadBufferPositiveExt2_patched);
	s_hooks[9] = taiHookFunctionExportForKernel(KERNEL_PID, &s_hook_refs[9], "SceCtrl", 0xD197E3C7, 0x104ED1A7, sceCtrlPeekBufferNegative_patched);
	s_hooks[10] = taiHookFunctionExportForKernel(KERNEL_PID, &s_hook_refs[10], "SceCtrl", 0xD197E3C7, 0x81A89660, sceCtrlPeekBufferNegative2_patched);
	s_hooks[11] = taiHookFunctionExportForKernel(KERNEL_PID, &s_hook_refs[11], "SceCtrl", 0xD197E3C7, 0x15F96FB0, sceCtrlReadBufferNegative_patched);
	s_hooks[12] = taiHookFunctionExportForKernel(KERNEL_PID, &s_hook_refs[12], "SceCtrl", 0xD197E3C7, 0x27A0C5FB, sceCtrlReadBufferNegative2_patched);

	/* Hook调用过程事件处理函数 */
	s_hooks[13] = taiHookFunctionImportForKernel(KERNEL_PID, &s_hook_refs[13], "SceProcessmgr", 0x887F19D0, 0x414CC813, ksceKernelInvokeProcEventHandler_patched);

	startMainThread();

	return SCE_KERNEL_START_SUCCESS;
}

int module_stop(SceSize argc, const void *args)
{
	finishMainThread();

	int i;
	for (i = 0; i < MAX_HOOKS_NUM; i++)
	{
		if (s_hooks[i] >= 0)
			taiHookReleaseForKernel(s_hooks[i], s_hook_refs[i]);
	}
	if (s_framebuf_mutex_uid >= 0)
		ksceKernelDeleteMutex(s_framebuf_mutex_uid);
	if (s_procevent_mutex_uid >= 0)
		ksceKernelDeleteMutex(s_procevent_mutex_uid);

	return SCE_KERNEL_STOP_SUCCESS;
}
